/**
 * 
 */
package junior.db.dao.jdbc;

import junior.db.mapping.ColumnMapping;
import junior.db.mapping.TableMapping;
import junior.util.BeanUtils;
import junior.util.reflect.ReflectUtils;
import junior.util.value.ValueUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.Assert;

import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * RowMapper工厂类
 * 根据传入的参数类型返回一个RowMapper的实例，此实例将自动注入除了
 * 关联实体类以外的所以类中声明属性的值
 */
@SuppressWarnings("unchecked")
public class RowMapperFactory {
	private final static Log logger = LogFactory.getLog(RowMapperFactory.class);
	
	private TableMapping tableMapping;
	private ColumnMapping columnMapping;
	
	public RowMapperFactory(TableMapping tableMapping, ColumnMapping columnMapping) {
		this.tableMapping = tableMapping;
		this.columnMapping = columnMapping;
	}
	
	public <T> RowMapper<T> getRowMapper(Class<T> resultClass) {
		return (RowMapper<T>) new EntityRowMapper(new Class<?>[]{resultClass}, new String[]{}, tableMapping, columnMapping);
	}
	
	public RowMapper<Object[]> getObjectArrayRowMapper() {
		return new ObjectArrayRowMapper();
	}
	
	public RowMapper<Object> getRowMapper(Class<?>[] resultClasses, String[] prefixes) {
		return new EntityRowMapper(resultClasses, prefixes, tableMapping, columnMapping);
	}
	
	private static class ObjectArrayRowMapper implements RowMapper<Object[]> {
		@Override
		public Object[] mapRow(ResultSet rs, int index)
				throws SQLException {
			ResultSetMetaData rsmd = rs.getMetaData();
			int columnCount = rsmd.getColumnCount();
			
			Object[] rslt = new Object[columnCount];
			
			for (int i=0; i<columnCount; i++){
				rslt[i] = rs.getObject(i+1);
			}
			return rslt;
		}
	}
	
	/**
	 * 混合型的实体RowMapper实现
	 * 支持结果集返回多个实体类或实体类与简单类型混合
	 * @author bo.wu
	 * 
	 */
	private static class EntityRowMapper implements RowMapper<Object> {
		private ColumnMapping columnMapping;
		private Map<String, Class<?>> prefixClassMap; // 前缀与类型Map
		private Map<String, Boolean> isBeanTypeMap; // 前缀与是否为实体类型Map
		private Map<Class<?>, String> entityTableMap;
		private Map<String, Integer> arrayTypeIdxMap;
		private boolean isMulti; // 结果集类型是否有多个

		private Map<String, Object[]> nameMap = new HashMap<String, Object[]>();
		
		public EntityRowMapper(Class<?>[] typeClasses, String[] prefixes,
                               TableMapping tableMapping, ColumnMapping columnMapping) {
			Assert.notNull(typeClasses, "Parameter typeClasses is required");
			
			isMulti = typeClasses.length > 1;
			
			int idx = 0;
			prefixClassMap = new LinkedHashMap<String, Class<?>>(typeClasses.length);
			isBeanTypeMap = new LinkedHashMap<String, Boolean>(typeClasses.length);
			entityTableMap = new HashMap<Class<?>, String>(typeClasses.length);
			boolean hasArrayType = false;
			for (Class<?> typeClazz : typeClasses) {
				String prefix = prefixes != null && prefixes.length > 0 ? prefixes[idx] : null;
				Boolean isBeanType = BeanUtils.isNotDefinedBean(typeClazz) ? Boolean.FALSE : Boolean.TRUE;
				isBeanTypeMap.put(prefix, isBeanType);
				prefixClassMap.put(prefix, typeClazz);
				if (isBeanType.booleanValue()) entityTableMap.put(typeClazz, tableMapping.entity2TableName(typeClazz));
				if (typeClazz.isArray()) {
					hasArrayType = true;
				}
				idx ++;
			}
			
			if (hasArrayType) {
				arrayTypeIdxMap = new HashMap<String, Integer>();
			}
			
			this.columnMapping = columnMapping;
		}

		@Override
		public Object mapRow(ResultSet rs, int index) throws SQLException {
			Map<String, Object> prefixInstMap = null;
			
			try {
				if (nameMap.size()<1) {
					buildNameAndTypes(rs);
				}
				
				prefixInstMap = new LinkedHashMap<String, Object>(prefixClassMap.size());
				
				for (String prefix : prefixClassMap.keySet()) {
					if (isBeanTypeMap.get(prefix).booleanValue()) {
						prefixInstMap.put(prefix, prefixClassMap.get(prefix).newInstance());
					} else if (prefixClassMap.get(prefix).isArray()) {
						Object[] array = new Object[arrayTypeIdxMap.get(prefix) + 1];
						prefixInstMap.put(prefix, array);
					} else {
						prefixInstMap.put(prefix, null); //占位
					}
				}
				
				for (Iterator<String> ite = nameMap.keySet().iterator(); ite.hasNext();) {
					String name = ite.next();
					Object[] nameAndType = nameMap.get(name);
					String fieldName = (String) nameAndType[0];
					Class<?> type = (Class<?>) nameAndType[1];
					
					Object value = getValue(rs, name, type);
					
					String prefix = getPrefix(name);
					
					Object _inst = prefixInstMap.get(prefix);
					Boolean _isBeanType = isBeanTypeMap.get(prefix);
					
					if (_isBeanType.booleanValue()) {
						ReflectUtils.callSetMethod(_inst, fieldName, type, ValueUtils.convert(value, type));
					} else if (type.isArray()) {
						Object[] array  = (Object[]) _inst;
						Integer idx = (Integer) nameAndType[2];
						array[idx] = value;
					} else {
						prefixInstMap.put(prefix, ValueUtils.convert(value, type));
					}
				}
			} catch(Exception e) {
				throw new RuntimeException(e.getMessage(), e);
			}
			
			if (isMulti) {
				return prefixInstMap.values().toArray();
			}
			
			return prefixInstMap.values().iterator().next();
		}

		private void buildNameAndTypes(ResultSet rs) throws SQLException,
				NoSuchMethodException {
			ResultSetMetaData rsmd = rs.getMetaData();
			int columnCount = rsmd.getColumnCount();
			
			for (int i=0; i<columnCount; i++) {
				String column = rsmd.getColumnLabel(i+1);
				if (column == null) column = rsmd.getColumnName(i+1);
				
				if ("RN".equalsIgnoreCase(column)) { // Resolve oracle page query bug
					continue;
				}
				
				String prefix = getPrefix(column);
				
				Class<?> clazz = prefixClassMap.get(prefix);
				Boolean isBeanType = isBeanTypeMap.get(prefix);
				if (isBeanType == null) {
					isBeanType = Boolean.FALSE;
				}
				
				Object[] nameAndType = null;
				if (isBeanType) {
					nameAndType = getFieldNameByColumnName(clazz, column);
				} else if (clazz.isArray()) {
					Integer idx = arrayTypeIdxMap.get(prefix);
					if (idx == null) {
						idx = 0;
					} else {
						idx = idx + 1;
					}
					nameAndType = new Object[]{column, clazz, idx};
					arrayTypeIdxMap.put(prefix, idx);
				} else {
					nameAndType = new Object[]{column, clazz};
				}
				
				if (nameAndType != null) {
					nameMap.put(column, nameAndType);
				}
			}
		}
		
		private Object[] getFieldNameByColumnName(Class<?> entityClass, String columnName) throws NoSuchMethodException {
			int idx = getPrefixIndex(columnName);
			if (idx != -1) {
				columnName = columnName.substring(idx + 2);
			}
			String fieldName = columnMapping.column2Field(entityTableMap.get(entityClass), columnName);
			try {
				Method setter = ReflectUtils.setter(entityClass, fieldName);
				if (setter != null){
					Object[] result = new Object[2];
					result[0] = fieldName;
					result[1] = setter.getParameterTypes()[0];
					return result;
				}
				logger.warn("Cannot found "+fieldName+" in "+entityClass.getName());
			} catch(NoSuchMethodException e){
				String msg = e.getMessage().toLowerCase();
				if ("rn".equals(msg) || msg.endsWith(".rn")) {
					//Ignore oracle page query row number
				} else {
					throw e;
				}
			}
			return null;
		}
		
		private String getPrefix(String name) {
			int idx = getPrefixIndex(name);
			if (idx != -1) {
				return name.substring(0, idx).toLowerCase();
			}
			return null;
		}
		
		private int getPrefixIndex(String name) {
			return name.indexOf("__");
		}
		
		/*
		 * 得到正确的值
		 * @param rs
		 * @param name 数据库返回结果集的列名，与实体字段名对应
		 * @param type 定义的数据类型
		 * @return
		 * @throws SQLException
		 */
		private Object getValue(ResultSet rs, String name, Class<?> type) throws SQLException {
            return JdbcHelper.getValue(rs, name, type);
		}
	}
	
}